package com.example.java_http_server.domain;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class HttpServer {
    /**
     * 构造函数,监听端口的连接
     * 可支持并发访问，使用线程池
     * @param port
     */
    public HttpServer(int port){
        if(port<1||port>65535){
            throw new RuntimeException("端口监听错误！");
        }
        try {
            //HTTP服务器，用于接收请求
            ServerSocket serverSocket = new ServerSocket(port);
            //构造线程池,固定为5个线程
            ExecutorService pool = Executors.newFixedThreadPool(5);
            //循环监听是否有连接
            while(true){
                //服务端进行阻塞，等待客户端连接，返回一个套接字
                Socket socket = serverSocket.accept();
                if(socket!=null && !socket.isClosed()){
                    Runnable task= ()->{//创建一个runnable任务来处理客户端请求
                        handleClient(socket);
                    };
                    pool.submit(task);
                }
            }
            

        } catch (Exception e) {
            log.error("Http服务器错误，exception：",e);
            throw new RuntimeException(e);
        }
    }
    /**
     * 处理客户端请求
     * 1、记录请求的时间，IP，路径，UA
     * 2、支持简单的Content-Type判别和Keep-Alive
     * 3、支持浏览器访问/hello返回HelloWorld!
     * 4、支持GET和POST请求(返回不同消息)
     * 5、能输出请求中的Header和Body
     * 6、支持404响应逻辑
     * @param socket
     */
    private void handleClient(Socket socket){
        try(
            //输入流，用于输入数据
            InputStream in = socket.getInputStream();
            //输出流，用于输出数据
            OutputStream out = socket.getOutputStream();
            //使用带缓冲的字符流
            BufferedReader reader = new BufferedReader(new InputStreamReader(in))
        ){
            boolean keepAlive=true;
            //循环，只要客户端和服务端都希望保持连接，就继续处理请求
            while(keepAlive && !socket.isClosed()){
                //记录请求时间
                long requestTime = System.currentTimeMillis();
                //记录IP
                String clientIP = socket.getInetAddress().getHostAddress();
                //读取首行，有数据的情况下为 GET /test1.html HTTP/1.1
                String firstLine = reader.readLine();
                if(firstLine==null){
                    //客户端连接关闭
                    break;
                }
                if(firstLine.length()==0){
                    writeResponse(out,400,"Bad Request","text/html","<h1>Bad Request</h1>");
                    continue;
                }
                String[] firstLineArray = firstLine.split(" ");
                if(firstLineArray.length!=3){
                    writeResponse(out,400,"Bad Request","text/html","<h1>Bad Request</h1>");
                    continue;
                }
                //请求方式
                String method = firstLineArray[0];
                //请求路径
                String requestUrl = firstLineArray[1];
                //协议
                String protocol = firstLineArray[2];
                //解析请求头
                HashMap<String, String> headers = new HashMap<>();
                String line;
                while((line=reader.readLine())!=null && !line.isEmpty()){
                    int index=line.indexOf(":");
                    if(index > 0){
                        String key=line.substring(0,index).trim();
                        String value=line.substring(index+1).trim();
                        headers.put(key.toLowerCase(),value);
                    }
                }
                //读取请求体(针对post请求)
                String requestBody="";
                if("POST".equalsIgnoreCase(method)&&headers.containsKey("content-length")){
                    //请求体字节数组长度
                    int contentLength=Integer.parseInt(headers.get("content-length"));
                    //创建一个长度为contentLength的字符数组,因为是字符流
                    char[] bodyChar=new char[contentLength];
                    int bytesRead=0;
                    //缓冲字节流可能不能一次性读取所有的数据，默认为8k
                    while(bytesRead<contentLength){
                        int read=reader.read(bodyChar,bytesRead,contentLength-bytesRead);
                        if(read==-1)break;
                        bytesRead+=read;
                    }
                    requestBody=new String(bodyChar);
                }
                //读取UA
                String userAgent = headers.getOrDefault("user-agent", "unknown");
                //打印日志
                logRequest(requestTime,clientIP,method,requestUrl,userAgent,headers,requestBody);

                // 判断是否使用长连接
                boolean requestKeepAlive = isKeepAlive(protocol, headers);
                keepAlive = requestKeepAlive;
                
                //响应
                if("/hello".equals(requestUrl)){
                    writeResponse(out,200,"OK","text/html",
                            "<h1>Hello World!</h1>",requestKeepAlive);
                }
                // 处理其他路径（添加404支持）
                if ("GET".equalsIgnoreCase(method)) {
                    writeResponse(out, 404, "Not Found", "text/html",
                            "<h1>404 Not Found</h1><p>get Path " + requestUrl + " not found</p>",requestKeepAlive);
                } else if ("POST".equalsIgnoreCase(method)) {
                    writeResponse(out, 404, "Not Found", "text/html",
                            "<h1>404 Not Found</h1><p>post Path " + requestUrl + " not found</p>",requestKeepAlive);
                } else {
                    // 不支持的方法
                    writeResponse(out, 405, "Method Not Allowed", "text/html",
                            "<h1>Method " + method + " not supported</h1>",requestKeepAlive);
                }
            }
        }catch (Exception e) {
            log.error("处理客户端请求错误，exception：",e);
        }finally {
            try {
                socket.close();
            } catch (Exception e) {
                log.error("关闭socket错误，exception：",e);
            }
        }
    }
    
    //日志输出
    private void logRequest(long requestTime, String clientIP, String method, String requestUrl,
                            String userAgent, Map<String,String> headers,String requestBody){
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String formattedTime = dateFormat.format(new Date(requestTime));
        log.info("请求时间:{},IP:{},方法:{},路径:{},UA:{}",formattedTime,clientIP,method,requestUrl,userAgent);
        log.info("请求头:");
        headers.forEach((k,v)->{
            log.info("  {},{}",k,v);
        });
        if(!requestBody.isEmpty()){
            log.info("请求体:{}",requestBody);
        }
    }
    
    //重载方法，支持指定是否使用长连接
    private void writeResponse(OutputStream out,int status,String desc,String contentType,String content,boolean keepAlive) throws IOException {
        StringBuilder headerBuilder = new StringBuilder();
        headerBuilder.append("HTTP/1.1 ").append(status).append(" ").append(desc).append("\r\n");
        headerBuilder.append("Content-Type: ").append(contentType).append(";charset=UTF-8\r\n");
        headerBuilder.append("Content-Length: ").append(content.getBytes().length).append("\r\n");
        // 设置Connection头
        headerBuilder.append("Connection: ").append(keepAlive ? "keep-alive" : "close").append("\r\n");
        // 为长连接添加Keep-Alive参数
        if (keepAlive) {
            headerBuilder.append("Keep-Alive: timeout=15, max=100\r\n");
        }
        headerBuilder.append("\r\n");
        out.write(headerBuilder.toString().getBytes());
        out.write(content.getBytes());
        out.flush();
        //由上层关闭输出流
    }
    //默认使用close连接
    private void writeResponse(OutputStream out,int status,String desc,String contentType,String content) throws IOException {
        writeResponse(out,status,desc,contentType,content,false);
    }
    //判断是否使用长连接
    private boolean isKeepAlive(String protocol, Map<String,String> headers){
        boolean isHttp11=protocol.equalsIgnoreCase("HTTP/1.1");
        String connection=headers.getOrDefault("connection",isHttp11?"keep-alive":"close").toLowerCase();
        return isHttp11?!"close".equals(connection):"keep-alive".equals( connection);
    }
    //复杂在于长连接的处理
    public static void main(String[] args){
        new HttpServer(8080);//监听的端口号
    }
}
